use tracing_subscriber::filter::{Targets};
use tracing_core::{Level};
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, Layer,filter::LevelFilter};
use tracing_subscriber::fmt::time::FormatTime;
use tracing_subscriber::fmt::format::Writer;
use tracing_appender::{non_blocking, rolling::{RollingFileAppender,Rotation}};
use chrono::prelude::*;
use std::error::Error;
use std::{result, vec};
use serde::{Serialize, Deserialize};

pub struct LocalLoggerTime;

type FmtError = std::fmt::Error;

impl FormatTime for LocalLoggerTime {
    fn format_time(&self, w: &mut Writer<'_>) -> result::Result<(), FmtError> {     
        let now = Local::now().format("%Y-%m-%d %H:%M:%S%.6f").to_string();
        write!(w,"{}", now)
    }
}

#[derive(Debug,Copy, Clone, Serialize,Deserialize)]
pub enum LogLevel {
    None,
    Trace,
    Debug,
    Info,
    Warn,
    Error,
    Final,
}

impl Default for LogLevel {
    fn default() -> Self {
        Self::Info
    }
}


impl From<LogLevel> for tracing::Level{
    fn from(value: LogLevel) -> Self {
        match value {
            LogLevel::Debug => Level::DEBUG,
            LogLevel::Error => Level::ERROR,
            LogLevel::Final => Level::ERROR,
            LogLevel::Trace => Level::TRACE,
            LogLevel::Warn => Level::WARN,
            _ => Level::INFO,
        }
    }
}

#[derive(Debug,Copy, Clone, Serialize,Deserialize)]
pub enum RollType {
    Single,
    Minutely,
    Hourly,
    Daily,
}

impl Default for RollType {
    fn default() -> Self {
        Self::Daily
    }
}

impl From<RollType> for Rotation {
    fn from(value: RollType) -> Self {
        match value {
            RollType::Daily => Rotation::DAILY,
            RollType::Minutely => Rotation::MINUTELY,
            RollType::Hourly => Rotation::HOURLY,
            _ => Rotation::NEVER,
        }
    }
}

#[derive(Debug, Clone, Serialize,Deserialize)]
pub struct LogConfigItem{
    pub name: String,
    pub roll: Option<RollType>,
    pub level: Option<LogLevel>,
    pub targets: Vec<String>, 
}

impl Default for LogConfigItem {
    fn default() -> Self {
        Self { 
            name: "common".to_owned(), 
            roll: Some(RollType::Single), 
            level: Some(LogLevel::default()),
            targets: vec![],
        }
    }
}

#[derive(Debug, Clone, Serialize,Deserialize)]
pub struct LogConfig{
    pub path: String,
    pub level: LogLevel,
    pub is_out_std: bool,    
    pub items: Vec<LogConfigItem>
}

impl Default for LogConfig {
    fn default() -> Self {
        Self {
            path: "log".to_owned(),
            level: LogLevel::Debug, 
            is_out_std: true,
            items: vec![LogConfigItem::default()]
        }
    }
}


impl LogConfig {
    pub fn  layers<S>(&self) -> (Vec<Box<dyn Layer<S> + Send + Sync + 'static>>,Vec<non_blocking::WorkerGuard>) 
    where
        S: tracing_core::Subscriber,
        for<'a> S: tracing_subscriber::registry::LookupSpan<'a>,
    {
        let mut layers = vec![];
        let mut guards = vec![];
        let lvl : tracing::Level = self.level.into();
        let level_filter: LevelFilter = lvl.into();
        if self.is_out_std {
            let io_layer  = fmt::layer()
            .pretty()
            .with_timer(LocalLoggerTime)
            .with_writer(std::io::stdout)
            .with_filter(level_filter)
            .boxed();
            layers.push(io_layer);
        }
        let path = self.path.as_str();
        self.items.iter().all(| it|{
            let name = it.name.as_str();
            let filter = Targets::new().with_targets( it.targets.iter().map(|x|{
                let l : tracing::Level = it.level.unwrap_or_default().into();
                (x.as_str(),l)
            }));
            let writer = RollingFileAppender::builder()
                .rotation(it.roll.unwrap_or_default().into())
                .utc(8,0,0) //# 默认时区
                .filename_prefix(name)
                .filename_suffix("log")
                .build(path)
                .expect("init file appender failed");

            let (appender,guard) = non_blocking(writer);

            let file_layer = fmt::layer()
            .with_timer(LocalLoggerTime)
            .with_ansi(false)
            .with_writer(appender)   
            .with_filter(filter).boxed();

            layers.push(file_layer);
            guards.push(guard);
            true
        });

        (layers,guards)
    }
}


pub fn logger_init(conf:&LogConfig) -> Result<Vec<tracing_appender::non_blocking::WorkerGuard>,Box<dyn Error>> {
    
	let logger = conf.layers();
    tracing_subscriber::registry()
    .with(logger.0)
    .init();
    Ok(logger.1)
    // // color_eyre::install().unwrap();
}



#[test]
fn test_set_logger_and_log(){
    use tracing::{debug, error, info, trace, warn};

    let config = LogConfig{
        path: "logs".to_owned(),
        level: LogLevel::Debug,
        is_out_std: true,
        items: vec![
            LogConfigItem{
                name: "common".to_owned(),
                roll: None,
                level: None,
                targets: vec!["my_crate".to_owned(),"other_crate".to_owned(),"*".to_owned()]
            },
            LogConfigItem{
                name: "sql".to_owned(),
                roll: Some(RollType::Hourly),
                level: None,
                targets: vec!["sql".to_owned()]
            },
            LogConfigItem{
                name: "other".to_owned(),
                roll: Some(RollType::Minutely),
                level: None,
                targets: vec!["my_crate".to_owned(),"my_crate::module".to_owned(),"other".to_owned(),"my_crate::cur".to_owned()]
            },
        ]
    };

    let _logger = logger_init(&config);

    tracing::info!("This is an info message");
    tracing::error!("This is an error message");

    info!(target: "my_crate","this is a my_crate message {}", 123);

    error!(target: "my_crate::cur","this is a message {}", 1234);

    info!(target: "other","this is a message {}", 123456);
    info!(target: "sql","this is a sql {}", 123787545);

    for x in 0..100{
        let times = x;
        trace!(target: "my_crate", times = x ,"{}: this is a my_crate trace message {}",x, 123 * x );
        debug!(target: "my_crate",times = x ,"{}: this is a my_crate debug message {}",x, 123 *2 * x);
        info!(target: "my_crate",times = x ,"{}: this is a my_crate info message {} system.time: {:?}",x, 123 * 3 * x, std::time::SystemTime::now());
        warn!(target: "my_crate",times = x ,"{}: this is a my_crate warn message {}",x, 123  * 4 * x);
        error!(target: "my_crate",times = x ,"{}: this is a my_crate error message {}",x, 123 * 5 * x);
    }

}